package cn.gyn.gynrpc.core.cluster;

import cn.gyn.gynrpc.core.api.Router;
import cn.gyn.gynrpc.core.meta.InstanceMeta;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 灰度路由
 * 因为经过router返回的还是一个集合，灰度是我们要从当前集群里找到当前要调的集合，是选一波而不是选某个，所以不放在RC或者filter里，而是放在router里
 * 功能扩展：可以做一些灰度用户，某一次请求加灰度标记，如果在灰度用户白名单上， 就直接返回
 **/
@Slf4j
@Data
public class GrayRouter implements Router<InstanceMeta> {

    private int grayRatio;
    private final Random random = new Random();

    public GrayRouter(int grayRatio) {
        this.grayRatio = grayRatio;
    }

    @Override
    public List<InstanceMeta> route(List<InstanceMeta> providers) {
        //灰度标记：ctx.gray = true?

        //简化处理
        if (providers == null || providers.size() <= 1) {
            return providers;
        }

        //对provider分类
        List<InstanceMeta> normalNodes = new ArrayList<>();
        List<InstanceMeta> grayNodes = new ArrayList<>();
        providers.forEach(p -> {
            if ("true".equals(p.getParameters().get("gray"))) {
                grayNodes.add(p);
            } else {
                normalNodes.add(p);
            }
        });

        //WOW !! 如果其中一个节点为空，就返回另一个,其实也就是其中一个是providers所有的数据，直接返回即可
        if (normalNodes.isEmpty() || grayNodes.isEmpty()) return providers;

        //处理grayRatio及其返回
        if (grayRatio <= 0) {
            return normalNodes;
        } else if (grayRatio >= 100) {
            return grayNodes;
        }

        log.debug("grayRouter grayNodes/normalNodes,grayRatio ===> {}/{},{}", grayNodes.size(), normalNodes.size(), grayRatio);

        //具体实现 grayRatio = 10/100
        //---------------------------------------------
        //方法1（不常用）：
        //grayNodes    1  03(假设是服务ID)
        //normalNodes  2  01,02
        //all 20 = 1*2=2（出现两次）, 2*9=18（两个各出现9次）（模拟一个总结点为20个，grayRatio=10的时候，返回的一个大集合）
        //03,03,01,02,01,02,01,02,01,02,01,02,01,02,01,02,01,02,01,02
        //缺点是节点数可能不好算百分比，比如3个grayNodes、4个normalNodes，10%灰度不好算虚拟节点的
        //并且要假设LB是线性均匀访问这个大集合的，不然LB可着某个节点访问，也会导致无法准确灰度
        //---------------------------------------------
        //方法2：
        //用radom随机数控制
        //在A情况下，返回normalNodes，保证LB本次就是Normal的；
        //在B情况下，返回grayNodes，保证LB本次就是gray的。从而能不受LB的规则的影响，实现grayRatio百分比
        if (random.nextInt(100) < grayRatio) {
            log.debug("grayRouter grayNodes ===> {}", grayNodes);
            return grayNodes;
        } else {
            log.debug("grayRouter normalNodes ===> {}", normalNodes);
            return normalNodes;
        }
    }
}
